Hermit Arcana: Huffman Coding & Job Sequencing
Tags: #◇CSALGCM #◇Problem Sets #◇HackerRank #◇Algorithms #◇Greedy Algorithms
◇Overview
The Hermit Arcana focuses on tree optimization and scheduling with deadlines: Huffman Coding (optimal prefix codes via min-heaps) and Job Sequencing with Deadlines (slot scheduling via greedy profit ordering).
◇Problem 1: Optimal Prefix Codes (Huffman Coding)
Problem Description
Given an alphabet of unique characters with corresponding frequencies , construct a binary prefix code tree that minimizes the expected weighted codeword length:
where is the depth (codeword length) of character in the tree. In a prefix code, no codeword is a prefix of any other codeword, allowing unambiguous stream decoding without delimiters.
Constraints:
Greedy Strategy & Proof Heuristic
- Maintain a min-priority queue (min-heap) of tree nodes keyed by total frequency.
- At each step, extract the two nodes and with the lowest frequencies.
- Allocate a new parent node with frequency , making its left child (bit 0) and its right child (bit 1).
- Re-insert into the min-heap. Repeat times until a single root node remains.
Optimality: Merging the two lowest-frequency symbols first places them at maximum tree depth, minimizing their contribution to the total bit count.
Stepped Trace
Consider character frequencies:
a: 45, b: 13, c: 12, d: 16, e: 9, f: 5 (Total characters = 100).
- Initial Min-Heap:
[f:5, e:9, c:12, b:13, d:16, a:45] - Merge
f:5ande:9Nodez1:14. Heap:[c:12, b:13, z1:14, d:16, a:45] - Merge
c:12andb:13Nodez2:25. Heap:[z1:14, d:16, z2:25, a:45] - Merge
z1:14andd:16Nodez3:30. Heap:[z2:25, z3:30, a:45] - Merge
z2:25andz3:30Nodez4:55. Heap:[a:45, z4:55] - Merge
a:45andz4:55Rootz5:100.
Resulting Codewords & Bits
| Char | Frequency | Codeword | Bit Length | Total Bits () |
|---|---|---|---|---|
| a | 45 | 0 | 1 | 45 |
| c | 12 | 100 | 3 | 36 |
| b | 13 | 101 | 3 | 39 |
| f | 5 | 1100 | 4 | 20 |
| e | 9 | 1101 | 4 | 36 |
| d | 16 | 111 | 3 | 48 |
Total Encoded Bits: 224 bits (compared to for fixed 3-bit encoding, saving 25.3%).
◇Problem 2: Job Sequencing with Deadlines
Problem Description
Given jobs, where each job has an integer deadline and a profit . Each job requires exactly 1 unit of time to execute on a single processor, and a job earns its profit only if it completes on or before its deadline (). Find the schedule that maximizes total profit.
Constraints:
Approach & Intuition
- Sort all jobs in descending order of profit: .
- For each job , greedily assign it to the latest possible vacant time slot .
- Delaying the execution of high-profit jobs as close to their deadlines as possible preserves earlier time slots for jobs with tighter deadlines.
Stepped Calculation Trace
Consider 5 jobs:
Max deadline = 3. Available time slots: [Slot 1, Slot 2, Slot 3].
- (): Slot 2 is vacant. Place at Slot 2. Timeline:
[_, J1, _]. - (): Slot 2 is occupied; check slot 1. Slot 1 is vacant. Place at Slot 1. Timeline:
[J3, J1, _]. - (): Slot 1 is occupied; cannot schedule. Reject.
- (): Slot 1 is occupied; cannot schedule. Reject.
- (): Slot 3 is vacant. Place at Slot 3. Timeline:
[J3, J1, J5].
Total Profit: .
◇Complete C++ Implementation
#include <iostream>
#include <vector>
#include <queue>
#include <string>
#include <algorithm>
using namespace std;
// --- Problem 1: Huffman Coding ---
struct HuffmanNode {
char ch;
int freq;
HuffmanNode *left, *right;
HuffmanNode(char c, int f) : ch(c), freq(f), left(nullptr), right(nullptr) {}
};
struct CompareNode {
bool operator()(HuffmanNode* a, HuffmanNode* b) {
return a->freq > b->freq;
}
};
void buildHuffmanCodes(HuffmanNode* root, string code, vector<pair<char, string>>& codes) {
if (!root) return;
if (!root->left && !root->right) {
codes.push_back({root->ch, code});
return;
}
buildHuffmanCodes(root->left, code + "0", codes);
buildHuffmanCodes(root->right, code + "1", codes);
}
vector<pair<char, string>> getHuffmanTree(const vector<pair<char, int>>& freqs) {
priority_queue<HuffmanNode*, vector<HuffmanNode*>, CompareNode> minHeap;
for (auto p : freqs) {
minHeap.push(new HuffmanNode(p.first, p.second));
}
while (minHeap.size() > 1) {
HuffmanNode* left = minHeap.top(); minHeap.pop();
HuffmanNode* right = minHeap.top(); minHeap.pop();
HuffmanNode* parent = new HuffmanNode('$', left->freq + right->freq);
parent->left = left;
parent->right = right;
minHeap.push(parent);
}
vector<pair<char, string>> codes;
buildHuffmanCodes(minHeap.top(), "", codes);
return codes;
}
// --- Problem 2: Job Sequencing with Deadlines ---
struct Job {
int id;
int deadline;
int profit;
};
pair<int, int> solveJobSequencing(vector<Job>& jobs) {
// Sort descending by profit
sort(jobs.begin(), jobs.end(), [](const Job& a, const Job& b) {
return a.profit > b.profit;
});
int maxDeadline = 0;
for (const auto& j : jobs) maxDeadline = max(maxDeadline, j.deadline);
vector<int> slot(maxDeadline + 1, -1);
int totalProfit = 0, countJobs = 0;
for (const auto& job : jobs) {
// Search backwards from job's deadline
for (int t = job.deadline; t >= 1; t--) {
if (slot[t] == -1) {
slot[t] = job.id;
totalProfit += job.profit;
countJobs++;
break;
}
}
}
return {countJobs, totalProfit};
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
// Test Huffman Coding
vector<pair<char, int>> freqs = {{'a', 45}, {'b', 13}, {'c', 12}, {'d', 16}, {'e', 9}, {'f', 5}};
auto codes = getHuffmanTree(freqs);
cout << "=== Huffman Generated Codewords ===\n";
for (auto c : codes) {
cout << c.first << ": " << c.second << "\n";
}
// Test Job Sequencing
vector<Job> jobs = {{1, 2, 100}, {2, 1, 19}, {3, 2, 27}, {4, 1, 25}, {5, 3, 15}};
auto [scheduled, profit] = solveJobSequencing(jobs);
cout << "\nJobs Scheduled: " << scheduled << ", Total Profit: " << profit << "\n"; // Output: 3, 142
return 0;
}
◇References & Navigation
- Previous Topic: ◇[PS] Chariot Arcana
- Next Topic: ◇[PS] Justice Arcana
- Related Notes: ◇Greedy Algorithms Introduction